

#import "ACRouter.h"
#import <objc/runtime.h>

@interface ACRouter ()
@property (strong, nonatomic) NSMutableDictionary *routes;
@end

@implementation ACRouter

+ (instancetype)shareRouter
{
    static ACRouter *router = nil;
    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{
        if (!router) {
            router = [[self alloc] init];
        }
    });
    return router;
}

+ (void)map:(NSString *)route toBlock:(ACRouterBlock)block
{
    NSMutableDictionary *subRoutes = [self.shareRouter subRoutesToRoute:route];

    subRoutes[@"_"] = [block copy];
}

- (UIViewController *)matchController:(NSString *)route
{
    route = [self normalizedURL:route];
    NSDictionary *params = [self paramsInRoute:route];
    Class controllerClass = params[@"controller_class"];
    UIViewController *viewController = [[controllerClass alloc] init];

    
    NSMutableDictionary* vcParams = [params mutableCopy];
    [vcParams removeObjectForKey:@"controller_class"];
    viewController.params = vcParams;
    return viewController;
}


+ (void)push:(NSString*)route{
    ACRouter* router = [self shareRouter];
    UITabBarController* tabbar = (UITabBarController*)[[UIApplication sharedApplication].delegate window].rootViewController;
    
    UIViewController* vc =  [router matchController:route];
    [tabbar.viewControllers[0] pushViewController:vc animated:YES];
}

+ (void)push:(NSString*)route WithParameters:(NSDictionary*)params{

    ACRouter* router = [self shareRouter];
    UITabBarController* tabbar = (UITabBarController*)[[UIApplication sharedApplication].delegate window].rootViewController;
    UIViewController* vc =  [router matchController:route];
    vc.params = params;
    [tabbar.viewControllers[0] pushViewController:vc animated:YES];

}

+ (void)present:(NSString*)route{
    UINavigationController* nav = (UINavigationController*)[[UIApplication sharedApplication].delegate window].rootViewController;
    UIViewController* topVc = nav.viewControllers.lastObject;
    UIViewController* vc =  [self.shareRouter matchController:route];
    if(!vc){
        
        [vc presentViewController:vc animated:YES completion:^{
            
        }];
        return;
    }
    [topVc presentViewController:vc animated:YES completion:^{
        
    }];
}






- (UIViewController *)match:(NSString *)route
{
    return [self matchController:[self normalizedURL:route]];
}

- (ACRouterBlock)matchBlock:(NSString *)route
{
    NSDictionary *params = [self paramsInRoute:route];
    
    if (!params){
    return nil;
    }
    
    ACRouterBlock routerBlock = [params[@"block"] copy];
    ACRouterBlock returnBlock = ^id(NSDictionary *aParams) {
        if (routerBlock) {
            NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithDictionary:params];
            [dic addEntriesFromDictionary:aParams];
            return routerBlock([NSDictionary dictionaryWithDictionary:dic].copy);
        }
        return nil;
    };
    
    return [returnBlock copy];
}

- (id)callBlock:(NSString *)route
{
    NSDictionary *params = [self paramsInRoute:route];
    ACRouterBlock routerBlock = [params[@"block"] copy];

    if (routerBlock) {
        return routerBlock([params copy]);
    }
    return nil;
}

// extract params in a route
- (NSDictionary *)paramsInRoute:(NSString *)route
{
    NSMutableDictionary *params = [NSMutableDictionary dictionary];

    NSMutableDictionary *subRoutes = self.routes;
    NSArray *pathComponents = [self pathComponentsFromRoute:route];
    for (NSString *pathComponent in pathComponents) {
        BOOL found = NO;
        NSArray *subRoutesKeys = subRoutes.allKeys;
        for (NSString *key in subRoutesKeys) {
            if ([subRoutesKeys containsObject:pathComponent]) {
                found = YES;
                subRoutes = subRoutes[pathComponent];
                break;
            } else if ([key hasPrefix:@":"]) {
                found = YES;
                subRoutes = subRoutes[key];
                params[[key substringFromIndex:1]] = pathComponent;
                break;
            }
        }
//        if (!found) {
//            return nil;
//        }
    }

    // Extract Params From Query.
    NSRange firstRange = [route rangeOfString:@"?"];
    if (firstRange.location != NSNotFound && route.length > firstRange.location + firstRange.length) {
        NSString *paramsString = [route substringFromIndex:firstRange.location + firstRange.length];
        NSArray *paramStringArr = [paramsString componentsSeparatedByString:@"&"];
        for (NSString *paramString in paramStringArr) {
            NSArray *paramArr = [paramString componentsSeparatedByString:@"="];
            if (paramArr.count > 1) {
                NSString *key = [paramArr objectAtIndex:0];
                NSString *value = [paramArr objectAtIndex:1];
                params[key] = value;
            }
        }
    }
    
    Class class = subRoutes[@"_"];
    if (class_isMetaClass(object_getClass(class))) {
        if ([class isSubclassOfClass:[UIViewController class]]) {
            params[@"controller_class"] = subRoutes[@"_"];
        } else {
            return nil;
        }
    } else {
        if (subRoutes[@"_"]) {
            params[@"block"] = [subRoutes[@"_"] copy];
        }
    }

    return [NSDictionary dictionaryWithDictionary:params];
}

#pragma mark - Private

- (NSMutableDictionary *)routes
{
    if (!_routes) {
        _routes = [[NSMutableDictionary alloc] init];
    }
    
    return _routes;
}

- (NSArray *)pathComponentsFromRoute:(NSString *)route
{
    NSMutableArray *pathComponents = [NSMutableArray array];

    NSRange range = [route rangeOfString:@"?"];
    if(range.location){
        [pathComponents addObjectsFromArray: [route componentsSeparatedByString:@"?"]];
//        if(pathComponents.count> 1){
//            [pathComponents removeLastObject];
//        }
    }
    
    return [pathComponents copy];
}



- (NSMutableDictionary *)subRoutesToRoute:(NSString *)route
{
    NSArray *pathComponents = [self pathComponentsFromRoute:route];

    NSInteger index = 0;
    NSMutableDictionary *subRoutes = self.routes;

    while (index < pathComponents.count) {
        NSString *pathComponent = pathComponents[index];
        if (![subRoutes objectForKey:pathComponent]) {
            subRoutes[pathComponent] = [[NSMutableDictionary alloc] init];
        }
        subRoutes = subRoutes[pathComponent];
        index++;
    }
    
    return subRoutes;
}

+ (void)map:(NSString *)route toControllerClass:(Class)controllerClass
{
    NSMutableDictionary *subRoutes = [self.shareRouter subRoutesToRoute:route];

    subRoutes[@"_"] = controllerClass;
}

- (ACRouteType)canRoute:(NSString *)route
{
    NSDictionary *params = [self paramsInRoute:route];
    
    if (params[@"controller_class"]) {
        return ACRouteTypeViewController;
    }
    
    if (params[@"block"]) {
        return ACRouteTypeBlock;
    }
    
    return ACRouteTypeNone;
}


static NSMutableCharacterSet* characterSet ;
+ (void)initialize{
    NSMutableCharacterSet* set =  [NSMutableCharacterSet  new];
    [set formUnionWithCharacterSet:[NSMutableCharacterSet URLHostAllowedCharacterSet]];
    [set formUnionWithCharacterSet:[NSMutableCharacterSet URLPathAllowedCharacterSet]];
    [set formUnionWithCharacterSet:[NSMutableCharacterSet URLQueryAllowedCharacterSet]];
    [set formUnionWithCharacterSet:[NSMutableCharacterSet URLFragmentAllowedCharacterSet]];
    characterSet = set;
    
}

- (NSString *)normalizedURL:(NSString *)aString {

    NSString *temp = [aString stringByReplacingOccurrencesOfString:@" " withString:@""];
    temp = [temp stringByReplacingOccurrencesOfString:@"\r" withString:@""];
    temp = [temp stringByReplacingOccurrencesOfString:@"\n" withString:@""];
    return temp;
}


@end




#pragma mark - UIViewController Category

@implementation UIViewController (ACRoute)

static char kAssociatedParamsObjectKey;


- (void)setParams:(NSDictionary *)paramsDictionary
{
    objc_setAssociatedObject(self, &kAssociatedParamsObjectKey, paramsDictionary, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
    NSArray* allKeys = paramsDictionary.allKeys;
        [allKeys enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            [self setValue:paramsDictionary[obj] forKey:obj];
        }];
    
}

- (NSDictionary *)params
{
    return objc_getAssociatedObject(self, &kAssociatedParamsObjectKey);
}

@end
